package pira.db.ui;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.index.RelationshipIndex;

import pira.elements.Author;
import pira.elements.Paper;
import pira.parser.xml.dblp.XMLFilesReader.MyRelationshipTypes;
import pira.utils.Log;

public class SubDBUI extends Neo4jDBUI{
	// configuration for database
	// default database path
	public static final String DEFAULTDATABASE = "../DB/subdb/";

	// configuration file
	public static final String CONFIG = "data/neo4j_config.props";

	// the global node
	public static final long GLOBALNODEID = 1;
	
	// the external node
	public static final long EXTERNALNODEID = 2;

	// index for papers
	public Index<Node> papers;

	// index for authors
	public Index<Node> authors;

	// index for relationships
	public RelationshipIndex writes;
	public RelationshipIndex cites;

	// constant of database
	public static final String ALLNODENUMBER = "all node number";
	public static final String PAPERNUMBER = "paper number";
	public static final String AUTHORNUMBER = "author number";
	public static final String PAPERFOUNDINCSXNUMBER = "paper found in citeseerx";
	
	// constant for index
	public static final String PAPERS = "papers";
	public static final String AUTHORS = "authors";
	public static final String WRITES = "writes";
	public static final String CITES = "cites";
	
	// global constant
	public static final long PAPERFOUNDINCSX = 48985;
	
	// constant for index
	public static final String INDEX = "index";

	// variables used to note paper and author's number
	public long paperNumber;
	public long authorNumber;
	
	public SubDBUI() {
		super(DEFAULTDATABASE, CONFIG);
		tx = graphDb.beginTx();
		try {
			index = graphDb.index();

			this.papers = index.forNodes(PAPERS);
			this.authors = index.forNodes(AUTHORS);
			this.writes = index.forRelationships(WRITES);
			this.cites = index.forRelationships(CITES);

			Node gnode = graphDb.getNodeById(GLOBALNODEID);
			this.paperNumber = (Long) gnode.getProperty(PAPERNUMBER);
			this.authorNumber = (Long) gnode.getProperty(AUTHORNUMBER);
			tx.success();
		} catch (org.neo4j.graphdb.NotFoundException e) {
			this.paperNumber = 0;
			this.authorNumber = 0;
		} finally {
			tx.finish();
		}
	}

	public SubDBUI(String database, String config) {
		super(database, config);
		tx = graphDb.beginTx();
		try {
			index = graphDb.index();

			this.papers = index.forNodes(PAPERS);
			this.authors = index.forNodes(AUTHORS);
			this.writes = index.forRelationships(WRITES);
			this.cites = index.forRelationships(CITES);

			Node gnode = graphDb.getNodeById(GLOBALNODEID);
			this.paperNumber = (Long) gnode.getProperty(PAPERNUMBER);
			this.authorNumber = (Long) gnode.getProperty(AUTHORNUMBER);
			tx.success();
		} catch (org.neo4j.graphdb.NotFoundException e) {
			this.paperNumber = 0;
			this.authorNumber = 0;
		} finally {
			tx.finish();
		}
	}
	
	public void createFromFullDB(GraphDBUI gdbui) {
		long newId = 0;
		long oldId = 0;
		long paperCount = 0;
		tx = graphDb.beginTx();
		Log ids = new Log("outputs/old_new_ID.txt");
		String str= null;
		BufferedReader br = null;
		try {
			newId = this.createGlobalNode();
			newId = this.createExternalNode();

			// read paper_find_in_citeseerx.txt
			FileReader fr = new FileReader(new File(
					"outputs/Paper_found_in_CiteSeerX.txt"));
			br = new BufferedReader(fr);
			String line = null;
			Paper paper = new Paper();
			while (br.ready()) {
				paperCount++;
				line = br.readLine();
				oldId = Long.parseLong(line);
				Node oldNode = gdbui.getGraphDatabaseService().getNodeById(
						oldId);
				paper.setPaperFromOldNode(oldNode);
System.out.println(paperCount + " th Normal Title : " + paper.normalTitle);
				newId = this.addPaperToDB(paper);
				
				// write the old and new IDs to log 
				str = paperCount + "\t" + oldId + "\t" + newId + "\n";
				ids.write(str);
				System.out.print(str);
				
				// keep a transaction handle the maximum 10000 papers
				if (paperCount % 10000 == 0) {
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			
			Node gnode = graphDb.getNodeById(GLOBALNODEID);
			gnode.setProperty(ALLNODENUMBER, paperNumber + authorNumber + 2);
			gnode.setProperty(PAPERNUMBER, paperNumber);
			gnode.setProperty(AUTHORNUMBER, authorNumber);
			tx.success();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				br.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			ids.close();
			tx.finish();
		}
	}
	
	/*
	 * Add a Java object Author to Database
	 */
	public long addAuthorToDB(Author author) {
		Node node = graphDb.createNode();

		// author number increase by one
		this.authorNumber++;
		
		// set properties for author
		node.setProperty(Author.AUTHORID, author.getNormalName().hashCode());
		node.setProperty(Author.FULLNAME, author.getFullName());

		if (author.getFirstName() != null)
			node.setProperty(Author.FIRSTNAME, author.getFirstName());
		else
			node.setProperty(Author.FIRSTNAME, NOT_EXIST);

		if (author.getMiddleName() != null)
			node.setProperty(Author.MIDDLENAME, author.getMiddleName());
		else
			node.setProperty(Author.MIDDLENAME, NOT_EXIST);

		if (author.getLastName() != null)
			node.setProperty(Author.LASTNAME, author.getLastName());
		else
			node.setProperty(Author.LASTNAME, NOT_EXIST);

		// add this author to index authors
		authors.add(node, Author.AUTHORID, author.getNormalName().hashCode());
		authors.add(node, Author.NAME, author.getNormalName());
		authors.add(node, Author.ORDER, this.authorNumber);
		
		return node.getId();
	}

	/*
	 * Add the Paper object to database
	 */
	protected long addPaperToDB(Paper paper) {
		Node node = graphDb.createNode();

		// paper number increase by one
		this.paperNumber++;
		
		// set properties for the paper node
		node.setProperty(Paper.PAPERID, paper.normalTitle.hashCode());
		if (paper.type != null)
			node.setProperty(Paper.TYPE, paper.type.toString());
		else
			node.setProperty(Paper.TYPE, NOT_EXIST);

		if (paper.key != null)
			node.setProperty(Paper.KEY, paper.key);
		else
			node.setProperty(Paper.KEY, NOT_EXIST);

		String[] names = new String[paper.getAuthors().size()];
		if (paper.getAuthors().size() != 0) {
			for (int i = 0; i < paper.getAuthors().size(); i++) {
				names[i] = paper.getAuthors().get(i).toString();
//				System.out.println(names[i]);

				
				// add the relationship of write/writtenBy
				IndexHits<Node> hits = authors.get(Author.AUTHORID,
						normal(names[i]).hashCode());
				
				Node authorNode = null;
				
				if (hits.size() == 0) {
					Author author = new Author(names[i]);
					long authorId = this.addAuthorToDB(author);
					authorNode = graphDb.getNodeById(authorId);
				} else
					authorNode = hits.getSingle();

				Relationship write = authorNode.createRelationshipTo(node,
						MyRelationshipTypes.WRITE);
				write.setProperty(Paper.TITLE, paper.getTitle());
				writes.add(write, INDEX, names[i] + "_" + paper.getTitle());
			}
		}
		node.setProperty(Paper.AUTHORS, names);

		if (paper.editor != null)
			node.setProperty(Paper.EDITOR, paper.editor);
		else
			node.setProperty(Paper.EDITOR, NOT_EXIST);

		if (paper.getTitle() != null)
			node.setProperty(Paper.TITLE, paper.getTitle());
		else
			node.setProperty(Paper.TITLE, NOT_EXIST);

		if (paper.publisher != null)
			node.setProperty(Paper.PUBLISHER, paper.publisher);
		else
			node.setProperty(Paper.PUBLISHER, NOT_EXIST);

		if (paper.mdate != null) {
			DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
			node.setProperty(Paper.MODIFYDATE, df.format(paper.mdate));
		} else
			node.setProperty(Paper.MODIFYDATE, NOT_EXIST);

		if (paper.bookTitle != null)
			node.setProperty(Paper.BOOKTITLE, paper.bookTitle);
		else
			node.setProperty(Paper.BOOKTITLE, NOT_EXIST);

		if (paper.cdrom != null)
			node.setProperty(Paper.CDROM, paper.cdrom);
		else
			node.setProperty(Paper.CDROM, NOT_EXIST);

		if (paper.pages != null)
			node.setProperty(Paper.PAGES, paper.pages);
		else
			node.setProperty(Paper.PAGES, NOT_EXIST);

		if (paper.paperURL != null)
			node.setProperty(Paper.PAPERURL, paper.paperURL);
		else
			node.setProperty(Paper.PAPERURL, NOT_EXIST);

		if (paper.isbn != null)
			node.setProperty(Paper.ISBN, paper.isbn);
		else
			node.setProperty(Paper.ISBN, NOT_EXIST);

		if (paper.citedTimes != -1)
			node.setProperty(Paper.CITEDTIMES, paper.citedTimes);
		else
			node.setProperty(Paper.CITEDTIMES, -1);

		if (paper.cite != null)
			node.setProperty(Paper.CITE, paper.cite);
		else
			node.setProperty(Paper.CITE, NOT_EXIST);

		if (paper.series != null)
			node.setProperty(Paper.SERIES, paper.series);
		else
			node.setProperty(Paper.SERIES, NOT_EXIST);

		if (paper.ee != null)
			node.setProperty(Paper.EE, paper.ee);
		else
			node.setProperty(Paper.EE, NOT_EXIST);

		if (paper.crossref != null)
			node.setProperty(Paper.CROSSREF, paper.crossref);
		else
			node.setProperty(Paper.CROSSREF, NOT_EXIST);

		// add this node to papers index
		papers.add(node, Paper.PAPERID, paper.normalTitle.hashCode());
		papers.add(node, Paper.TITLE, paper.normalTitle);
		papers.add(node, Paper.ORDER, this.paperNumber);
		
		return node.getId();
	}

	// create global node the metadata of the database
	protected long createGlobalNode() {
		Node gnode = graphDb.createNode();
		gnode.setProperty(ALLNODENUMBER, 2);
		gnode.setProperty(PAPERNUMBER, 0);
		gnode.setProperty(AUTHORNUMBER, 0);
		gnode.setProperty(PAPERFOUNDINCSXNUMBER, PAPERFOUNDINCSX);
		return gnode.getId();
	}
	
	// create external node
	protected long createExternalNode() {
		Node externalNode = graphDb.createNode();
		externalNode.setProperty(Paper.TITLE, EXTERNAL);
		externalNode.setProperty(Paper.TYPE, EXTERNAL);

		long id = externalNode.getId();

		return id;
	}
	
	/*
	 *  normal a string, replace all the no-letter to space
	 *  delete the continuous 2 or more space
	 *  change them to lower case
	 */
	
	public static String normal(String src) {
		String normal = null;
		Pattern p = Pattern.compile("\\W");
		Matcher m = p.matcher(src);
		normal = m.replaceAll(" ");
		p = Pattern.compile(" {2,}");
		m = p.matcher(normal);
		normal = m.replaceAll(" ");
		normal = normal.toLowerCase().trim();
		// System.out.println(normal);
		return normal;
	}
	
	public void addCitation(GraphDBUI gdbui, Node myNode, String title, String[] names) {
		// my title is cited by titles
		String str = null;
		String myTitle = (String) myNode.getProperty(Paper.TITLE);
		Log citation = new Log("outputs/Add_citation.txt", true);

		Node node = null;
		long id = -1;
//System.out.println(title);
//for (String name : names)
//	System.out.println(name);
		
		node = findPaperInDB(papers, title, names, false);
		
		if (node != null) {
			// if found in SubDB
			id = node.getId();
//System.out.println("found in subdb!");
		} else {
			node = findPaperInDB(gdbui.papers, title, names, true);
			if (node != null) {
				// if found in old DB, copy this node from old DB to subDB
//System.out.println("found in oldDB!");
				Paper paper = new Paper();
				paper.setPaperFromOldNode(node);
				id = addPaperToDB(paper);
				node = graphDb.getNodeById(id);
			} else {
//System.out.println("not found both!");
				Paper paper = new Paper(title, names);
				id = addPaperToDB(paper);
				node = graphDb.getNodeById(id);
			}
		}
		
		// add citation between two nodes
		addCitationBetween2Nodes(myNode, myTitle, node, title);
		
		// record to log
		str = "\tAdd citation from (" + node.getId() + "," + title + ") to (" 
				+ myNode.getId() + "," + myTitle + ").\n\n";
		
		citation.write(str);
		
		citation.close();
	}
	
	/*
	 * Add the citation between two nodes in database
	 */
	private void addCitationBetween2Nodes(Node myNode, String myTitle,
			Node node, String title) {
		String value = title + "_" + myTitle;
		IndexHits<Relationship> hits = cites.get(INDEX, value);
		boolean exist = false;
		Relationship cite = null;
		
		while (hits.hasNext() && !exist) {
			cite = hits.next();
			if (cite.getStartNode().getId() == node.getId()
					&& cite.getEndNode().getId() == myNode.getId()) {
				exist = true;
			}
		}
		
		if (!exist) {
			cite = node.createRelationshipTo(myNode, 
					MyRelationshipTypes.CITE);

			// set the property and add to index
			cite.setProperty(INDEX, value);
			cites.add(cite, INDEX, value);
		} 
		// else 
		// citation exists already
	}

	public Node findPaperInDB(Index<Node> pools, String title, String[] names, boolean old) {
		IndexHits<Node> hits;
		
		if (!old) {
			// find if the title is in DB
			hits = pools.get(Paper.TITLE, normal(title));
//			anotherHits = pools.get(Paper.TITLE, normal(title));
//			hits = pools.get(Paper.PAPERID, normal(title).hashCode());
//			anotherHits = pools.get(Paper.PAPERID, normal(another).hashCode());
		} else {
			// search in old DB
			hits = pools.get("paperID", title.hashCode());
			if (hits.size() == 0) {
				String another = title + ".";
				IndexHits<Node> anotherHits = null;
				anotherHits = pools.get("paperID", another.hashCode());
				hits = anotherHits;
			}
		}
		
		Node node = null;
		if (hits.size() != 0) {
			/*
			 * if find in DB, compare authors
			 */
			String[] authors = null;
			if (hits.size() != 0) {
				// if there's at least one author match, we trait them as the
				// same
				lable: for (Node p : hits) {
//					System.out.println(p.getId());
					authors = (String[]) p.getProperty(Paper.AUTHORS);
					for (String name : names)
						for (String author : authors)
							if (name.equals(author)) {
								node = p;
								break lable;
							}
				}
			}
		} 
		return node;
	}
	
	/*
	 * give a brief summary for the database
	 */
	public void summary(long beginIndex, long endIndex) {
		Node node = null;
		long id = beginIndex;
		int paperNumber = 0;
		int authorNumber = 0;
		int allNumber = 0;

		while (id < endIndex) {
			node = graphDb.getNodeById(id);
			allNumber++;
			if (node.hasProperty("full name"))
				authorNumber++;
			else if (node.hasProperty("title")) {
				String title = (String) node.getProperty("title");
				if (!title.equals(EXTERNAL))
					paperNumber++;
			}
			id++;
		}


		System.out.println("Totally there're " + allNumber + " nodes.");
		System.out.println("\t" + paperNumber + " papers.");
		System.out.println("\t" + authorNumber + " authors.");
	}
	
	public void summary() {
		Iterator<Node> all = graphDb.getAllNodes().iterator();
		Node node = null;
		long allNumber = 0;
		long paperNumber = 0;
		long authorNumber = 0;
		
		// pass the zero node ,global node and external node
		all.next();
		all.next();
		allNumber++;
		all.next();
		allNumber++;
		
		while (all.hasNext()) {
			node = all.next();
			allNumber++;
			if (node.hasProperty(Paper.TITLE))
				paperNumber++;
			else
				authorNumber++;
		}
		System.out.println("Totally, there are " + allNumber + " nodes.");
		System.out.println("\t" + paperNumber + " papers");
		System.out.println("\t" + authorNumber + " authors.");
	}
	
	public void search(String keyWords) {
		int count = 0;
		for (Node node : papers.query(Paper.TITLE, keyWords)) {
			System.out.println(node.getId());
			System.out.println(node.getProperty(Paper.TITLE));
			count++;
		}
		System.out.println("\n" + count + " results are found.");
	}
}
